//
// Mouse Gesture Recognizer
// (c) Robert Hahn / HTW Dresden
//
// Neuron.cs
// ---------
// Neuron class
//

using System;
using System.Collections;
using System.ComponentModel;


namespace CNeural
{
	/// <summary>
	/// Represents a single neuron in the neural network.
	/// </summary>
	[TypeConverter(typeof(ExpandableObjectConverter)), Serializable]
	public class Neuron
	{
		#region Fields
		protected int     id;             // unique number
		protected Random  random;
		protected bool    isUsingBias;
		protected double  biasWeight;
		protected double  lastBiasChange;
		protected double  input;          // input value (from input pattern)
		protected double  netInput;       // net input of the neuron
		protected double  activation;     // activation
		protected double  output;         // output value, f(a)
		protected double  error;
		protected int     index;

		protected IActivationFunction  activationFunction;
		protected IActivationFunction  outputFunction;		
		protected IInputFunction       inputFunction;		
		protected ConnectionList       inputConnections;
		protected ConnectionList       outputConnections;
		protected NeuronLayer          layer;

		// double expectedOutput;
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the ID of the neuron.
		/// </summary>
		public int Id 
		{
			get { return this.id; }
			set { this.id = value; }
		}
		

		/// <summary>
		/// Gets or sets the activation function.
		/// </summary>
		public IActivationFunction ActivationFunction 
		{
			get { return this.activationFunction; }
			set { this.activationFunction = value; }
		}


		/// <summary>
		/// Gets or sets the activation function.
		/// </summary>
		public IInputFunction InputFunction 
		{
			get { return this.inputFunction; }
			set { this.inputFunction = value; }
		}


		/// <summary>
		/// Gets or sets the weight of the bias.
		/// </summary>
		public double BiasWeight 
		{
			get { return this.biasWeight; }
			set { this.biasWeight = value; }
		}

		
		/// <summary>
		/// Gets or sets the last change of the bias weight.
		/// </summary>
		public double LastBiasChange 
		{
			get { return this.lastBiasChange; }
			set { this.lastBiasChange = value; }
		}


		/// <summary>
		/// Gets or sets the index.
		/// </summary>
		public int Index 
		{
			get { return this.index; }
			set { this.index = value; }
		}


		/// <summary>
		/// Gets or sets the neuron layer this neuron belongs to.
		/// </summary>
		[Browsable(false)]
		public NeuronLayer Layer 
		{
			get { return this.layer; }
			set { this.layer = value; }
		}


		/// <summary>
		/// Gets or sets an array of neurons in the predecessor layer.
		/// </summary>
		public ConnectionList InputConnections 
		{
			get { return this.inputConnections; }
			set { this.inputConnections = value; }
		}


		/// <summary>
		/// Gets or sets an array of neurons in the successor layer.
		/// </summary>
		public ConnectionList OutputConnections 
		{
			get { return this.outputConnections; }
			set { this.outputConnections = value; }
		}


		/// <summary>
		/// Gets or sets the last output value of the neuron. Use ComputeOutput() 
		/// to update the output value!
		/// </summary>
		public double Output 
		{
			get 
			{
				return this.output; 
			}
			set 
			{ 
				this.output = value; 
			}
		}

		
		/// <summary>
		/// Gets the output error of the neuron (difference between current 
		/// output and the expected output).
		/// </summary>
		public double OutputError 
		{
			get { return this.error; }
			set { this.error = value; }
		}


		/// <summary>
		/// Gets the input activation of the neuron, sum(weight[i] * output[i])
		/// </summary>
		public double Activation 
		{
			get { return this.activation; }
		}


		/// <summary>
		/// Gets or sets the input value for the neuron (only used in input 
		/// layers).
		/// </summary>
		public double Input 
		{
			get 
			{ 
				return this.input; 
			}
			set 
			{ 
				this.input = value; 
			}
		}


		/// <summary>
		/// Determines if the neuron is using a bias.
		/// </summary>
		public bool IsUsingBias 
		{
			get 
			{ 
				return this.isUsingBias; 
			}
			set 
            { 
				this.isUsingBias = value; 
			}
		}
		#endregion

		#region Constructors
		public Neuron(NeuronLayer layer, int index, IInputFunction inputFunction,
			IActivationFunction activationFunction, 
			bool isUsingBias, Random randomGenerator)
		{
			this.layer              = layer; 
			this.index              = index;
			this.activationFunction = activationFunction;
			this.inputFunction      = inputFunction;
			this.outputFunction     = null;
			this.random             = randomGenerator;

			this.isUsingBias = isUsingBias;
			if (this.isUsingBias) 
			{
				this.biasWeight = (double) (random.NextDouble() * 2.0 - 1.0);
			}
			this.inputConnections = new ConnectionList();
			this.outputConnections = new ConnectionList();
		}
		#endregion

		#region Methods
		/// <summary>
		/// Creates a connection from another neuron with random weight.
		/// </summary>
		/// <param name="source">The source neuron of the connection.</param>
		public void Connect(Neuron source) 
		{
			double weight = (double) (random.NextDouble() * 2.0 - 1.0);
			this.inputConnections.Add(new Connection(source, weight));

			source.outputConnections.Add(new Connection(this, weight));
		}


		/// <summary>
		/// Computes the output of the neuron.
		/// </summary>
		public void ComputeOutput()
		{
			if (this.inputFunction != null && 
				this.inputConnections.Count > 0) 
			{
				// call the input function to compute the net input from
				// the incoming connections
				this.netInput = this.inputFunction.Compute(this.inputConnections);
			}
			else 
			{
				// input layer (no input connections, use external input instead)
				this.netInput = this.input;
			}

			// add bias
			if (this.isUsingBias) {

				this.netInput += this.biasWeight;
			}
			
			// compute activation
			if (this.activationFunction != null)  
			{
				this.activation = this.activationFunction.Compute(this.netInput);
			}
			else 
			{
				this.activation = this.netInput;
			}
			
			// compute output
			if (this.outputFunction != null)  
			{
				this.output = this.outputFunction.Compute(this.activation);
			}
			else 
			{
				this.output = this.activation;
			}
		}

		/// <summary>
		/// Computes the error of neurons in the output layer.
		/// </summary>
		/// <param name="expectedOutput"></param>
		public void ComputeError(double expectedOutput) 
		{
			this.error = (expectedOutput - this.output) * 
				this.activationFunction.ComputeDerivation(this.netInput);
		}


		/// <summary>
		/// Computes the error of neurons in a hidden layer.
		/// </summary>
		public void ComputeError() 
		{
			double e = 0;
			foreach (Connection c in this.outputConnections) 
			{
				e += c.OtherNeuron.OutputError * c.Weight;
			}
			e *= this.activationFunction.ComputeDerivation(this.netInput);
			this.error = e;
		}


		/// <summary>
		/// Randomizes the weights of incoming connections and the bias and
		/// resets the last delta values.
		/// </summary>
		public void Randomize() 
		{
			foreach (Connection c in this.inputConnections) 
			{
				c.Weight = (double)(this.random.NextDouble() * 2.0 - 1.0);
				c.LastWeightChange = 0F;
			}
			if (this.isUsingBias) 
			{
				this.biasWeight =(double) (random.NextDouble() * 2.0 - 1.0);
				this.lastBiasChange = 0F;
			}
		}


		/// <summary>
		/// Creates a string describing the neuron.
		/// </summary>
		/// <returns></returns>
		public override string ToString() 
		{
			string s = "Neuron " + this.index + ":\n";
			for (int i = 0; i < this.InputConnections.Count; i++) 
			{
				s += "   Input connection " + i + ": " + 
				     Math.Round(this.InputConnections[i].Weight, 4) + 
				     " (last change : " + 
					 Math.Round(this.InputConnections[i].LastWeightChange, 4) + 
				     ")\n";
			}
			if (this.isUsingBias) 
			{
				s += "   Bias (Last Change) : " + Math.Round(this.BiasWeight, 4) + 
				     " (" + Math.Round(this.LastBiasChange, 4) + ")\n";
			}
			s += "   Net Input     : " + Math.Round(this.netInput, 4) + "\n";
			if (this.activationFunction != null) 
			{
				s += "   f-Activation  : " + this.ActivationFunction.Description + "\n";
				s += "   f'(A)         : " + this.activationFunction.ComputeDerivation(this.netInput) + "\n";
			}
			s += "   Activation    : " + Math.Round(this.Activation, 4) + "\n";
			s += "   Output = f(A) : " + Math.Round(this.Output, 4) + "\n";
			s += "   Error         : " + this.OutputError + "\n";
			return s;
		}
		#endregion
	}
}
